package com.nulldev.util.scripting.ScriptAPI;

import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.JVM.JVM;
import com.nulldev.util.JVM.reflections.Reflections;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.data.Arrays.arrays.FastArrayList;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.scripting.ScriptAPI.brainfuck.BrainfuckFactory;
import com.nulldev.util.scripting.ScriptAPI.plusExt.rhino.RhinoJSEFactory;

public class nScripting {
	private static final Logger log = LoggerFactory
			.getLoggerD(Arguments.hasArgument("--debug", "--trace", "-debug", "-trace", "--nScripting.debug", "-nScripting.debug"));
	private static final List<ScriptEngineFactory> se = new FastArrayList<ScriptEngineFactory>(true);
	private static final ScriptEngineManager se0;
	private static final boolean ALLOW_NATIVE_RHINO_SEF = true;

	static {
		if (JVM.version() >= 1.6f)
			se0 = manager();
		else
			se0 = null;
	}

	public static void main(String[] args) {
		JVM.println("Printing scripting engines. (" + se.size() + ")");
		ArrayUtils.printLln(se);
	}

	@SuppressWarnings("unchecked")
	public static ScriptEngineManager manager() {
		if (JVM.version() < 1.6) // This check is sort of useless...
			throw new UnsupportedOperationException("nScripting::manager --> This function is unsupported since you have a JDK that is before 1.6.");
		final ScriptEngineManager em = new ScriptEngineManager();
		if (Reflections.is("org.mozilla.javascript.Context") && JVM.version() <= 16f) {
			if (Reflections.is("org.mozilla.javascript.engine.RhinoScriptEngineFactory") && ALLOW_NATIVE_RHINO_SEF) {
				log.debug("nScripting+: Enabled Mozilla's Rhino JS Engine. [jse=native]");
				ScriptEngineFactory rjse;
				try {
					rjse = (ScriptEngineFactory) Reflections.get("org.mozilla.javascript.engine.RhinoScriptEngineFactory").newInstance();
					se.add(rjse);
					final Reflections _em = Reflections.encapsulate(em);
					final Object n = _em.declVar("engineSpis");
					((Set<ScriptEngineFactory>) n).add(rjse);
				} catch (Exception ex) {
					log.error("Failed to add Rhino's JSE!", ex);
				}
			} else {
				log.debug("nScripting+: Enabled Mozilla's Rhino JS Engine. [jse=local]");
				final RhinoJSEFactory rjse = new RhinoJSEFactory();
				se.add(rjse);
				final Reflections _em = Reflections.encapsulate(em);
				final Object n = _em.declVar("engineSpis");
				((Set<ScriptEngineFactory>) n).add(rjse);
			}
		}
		if (Reflections.is("com.nulldev.util.internal.externals.lebjs.scriptapi.LebJSEFactory") && JVM.version() <= 16f) {
			log.warn("nScripting+: Enabled LebJS Engine.");
			final ScriptEngineFactory ljse = (ScriptEngineFactory) Reflections.make("com.nulldev.util.internal.externals.lebjs.scriptapi.LebJSEFactory");
			se.add(ljse);
			final Reflections _em = Reflections.encapsulate(em);
			final Object n = _em.declVar("engineSpis");
			((Set<ScriptEngineFactory>) n).add(ljse);
		}
		if (Reflections.is("com.nulldev.util.scripting.ScriptAPI.brainfuck.BrainfuckFactory") && JVM.version() <= 16f) {
			log.debug("nScripting+: Enabled Brainfuck Engine.");
			final BrainfuckFactory bfe = new BrainfuckFactory();
			se.add(bfe);
			final Reflections _em = Reflections.encapsulate(em);
			final Object n = _em.declVar("engineSpis");
			((Set<ScriptEngineFactory>) n).add(bfe);
		}
		for (final ScriptEngineFactory e : se) {
			em.registerEngineName(e.getEngineName(), e);
			e.getMimeTypes().forEach((mimeType) -> {
				em.registerEngineMimeType(mimeType, e);
			});
			e.getExtensions().forEach((extension) -> {
				em.registerEngineExtension(extension, e);
			});
		}
		return em;
	}

	public static ScriptEngineManager cached() {
		if (JVM.version() < 1.6) // This check is sort of useless...
			throw new UnsupportedOperationException("nScripting::manager --> This function is unsupported since you have a JDK that is before 1.6.");
		if (se0 == null)
			return null;
		return se0;
	}

	private static final AtomicBoolean isGraalJSInstalled = new AtomicBoolean(), graal_tested = new AtomicBoolean();

	public static boolean isGraalJSInstalled() {
		if (graal_tested.get())
			return isGraalJSInstalled.get();
		try {
			final boolean x = cached().getEngineByName("graal.js") != null;
			isGraalJSInstalled.set(x);
			graal_tested.set(true);
			return x;
		} catch (Exception e) {
			JVM.errorln(e);
			return false;
		}
	}

	private static volatile boolean isNashornJSInstalled, nashorn_tested;

	public static boolean isNashornInstalled() {
		if (nashorn_tested)
			return isNashornJSInstalled;
		try {
			final boolean x = cached().getEngineByName("nashorn") != null;
			isNashornJSInstalled = x;
			nashorn_tested = true;
			return x;
		} catch (Exception e) {
			JVM.errorln(e);
			return false;
		}
	}

	private static volatile boolean isRhinoJSInstalled, rhino_tested;

	public static boolean isRhinoInstalled() {
		if (rhino_tested) {
			return isRhinoJSInstalled;
		}
		try {
			final boolean x = cached().getEngineByName("Rhino") != null;
			isRhinoJSInstalled = x;
			rhino_tested = true;
			return x;
		} catch (Exception e) {
			JVM.errorln(e);
			return false;
		}
	}

	public static String[] getAllJSEngines() {
		String[] s = new String[0];
		if (isNashornInstalled())
			s = ArrayUtils.add(s, "nashorn");
		if (isGraalJSInstalled())
			s = ArrayUtils.add(s, "graal.js");
		if (isRhinoInstalled())
			s = ArrayUtils.add(s, "Rhino");
		return s;
	}

	public static ScriptEngine getJSEngine() {
		if (isRhinoInstalled())
			return cached().getEngineByName("Rhino");
		if (isNashornInstalled())
			return cached().getEngineByName("nashorn");
		if (isGraalJSInstalled())
			return cached().getEngineByName("graal.js");
		// TODO: Add BaseJS here.
		throw new UnsupportedOperationException("No suitable JavaScript engine found!");
	}

	public static ScriptEngine[] engines() {
		final List<ScriptEngineFactory> y = cached().getEngineFactories();
		final ScriptEngine[] x = new ScriptEngine[y.size()];
		for (int i = 0; i < y.size(); i++) {
			final ScriptEngineFactory f = y.get(i);
			if (f == null)
				continue;
			x[i] = f.getScriptEngine();
		}
		return x;
	}

	public static ScriptEngineFactory[] factories() {
		final List<ScriptEngineFactory> y = cached().getEngineFactories();
		return y.toArray(new ScriptEngineFactory[y.size()]);
	}

	public static boolean hasEngine(final String string) {
		return hasEngineByName(string);
	}

	public static boolean hasEngineByName(final String string) {
		try {
			final ScriptEngine se = cached().getEngineByName(string);
			return se != null;
		} catch (Exception ex) {
			return false;
		}
	}

	public static boolean hasEngineByMimeType(final String string) {
		try {
			final ScriptEngine se = cached().getEngineByMimeType(string);
			return se != null;
		} catch (Exception ex) {
			return false;
		}
	}

	public static boolean hasEngineByExtension(final String string) {
		try {
			final ScriptEngine se = cached().getEngineByExtension(string);
			return se != null;
		} catch (Exception ex) {
			return false;
		}
	}

	public static ScriptEngine getEngineByName(final String string) {
		return se0.getEngineByName(string);
	}
}
